1.效果
裸眼3D
2.CMMotionManager 概述
用于启动和管理运动服务的对象。
1
| class CMMotionManager : NSObject
|
使用CMMotionManager
对象启动报告设备板载传感器检测到的运动的服务。使用此对象接收四种类型的运动数据:
- 加速度计数据,表示设备在三维空间的瞬时加速度。
- 陀螺仪数据,表示围绕设备三个主轴的瞬时旋转。
- 磁力计数据,指示设备相对于地球磁场的方向。
- 设备运动数据,指示与运动相关的关键属性,例如设备的用户启动加速度、其姿态、旋转速率、相对于校准磁场的方向以及相对于重力的方向。该数据由 Core Motion 的传感器融合算法提供。
处理后的设备运动数据给出了设备的姿态、旋转速率、校准磁场、重力方向以及用户赋予设备的加速度。
只为您的应用创建一个 CMMotionManager 对象。此类的多个实例会影响从加速度计和陀螺仪接收数据的速率。
您可以按指定的更新间隔接收实时传感器数据,也可以让传感器收集数据并将其存储以供以后检索。使用这两种方法,当您不再需要数据时调用适当的停止方法 (stopAccelerometerUpdates()
, stopGyroUpdates()
, stopMagnetometerUpdates()
,和 stopDeviceMotionUpdates()
) 。
以指定的间隔处理运动更新
为了在特定的时间间隔接收运动数据,app 调用一个“start”方法,该方法采用一个操作队列(OperationQueue的实例)和一个特定类型的block handler 来处理这些更新。运动数据被传递到block handler 中。更新频率由“interval”属性的值决定。
- 加速度计。设置
accelerometerUpdateInterval
属性以指定更新间隔。调用startAccelerometerUpdates(to:withHandler:)
该方法,传入一个CMAccelerometerHandler
类型的block。加速度计数据作为CMAccelerometerData
对象传递到block中。
- 陀螺仪。设置
gyroUpdateInterval
属性以指定更新间隔。调用startGyroUpdates(to:withHandler:)
该方法,传入一个CMGyroHandler
类型的块。旋转速率数据作为CMGyroData
对象传递到block中。
- 磁力计。设置
magnetometerUpdateInterval
属性以指定更新间隔。调用 startMagnetometerUpdates(to:withHandler:)
该方法,传递一个CMMagnetometerHandler
类型的block。磁场数据作为CMMagnetometerData
对象传递到block中。
- 设备运动。设置属性
deviceMotionUpdateInterval
以指定更新间隔。调用startDeviceMotionUpdates(using:)
或startDeviceMotionUpdates(using:to:withHandler:)
或 startDeviceMotionUpdates(to:withHandler:)
方法,传入一个CMDeviceMotionHandler
类型的块。旋转速率数据作为CMDeviceMotion
对象传递到block中。
1 2 3 4
| CMMotionManager *motionManager = [[CMMotionManager alloc] init]; [motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) { }];
|
运动数据的定期采样
为了通过周期性采样来处理运动数据,应用程序调用一个不带参数的“start”方法,并周期性地访问给定类型运动数据的属性所保存的运动数据。这种方法是游戏等应用程序的推荐方法。在一个block中处理加速度计数据会带来额外的开销,并且大多数游戏应用程序只对渲染帧时的最新运动数据样本感兴趣。
- 加速度计。调用
startAccelerometerUpdates()
以开始更新并通过读取accelerometerData
属性定期访问CMAccelerometerData
对象。
- 陀螺仪。 调用
startGyroUpdates()
以开始更新并通过读取gyroData
属性定期访问CMGyroData
对象。
- 磁力计。调用
startMagnetometerUpdates()
以开始更新并通过读取magnetometerData
属性定期访问CMMagnetometerData
对象。
- 设备运动。 调用
startDeviceMotionUpdates(using:)
或startDeviceMotionUpdates()
方法开始更新并通过读取deviceMotion
属性定期访问CMDeviceMotion
对象。该方法(iOS 5.0 中的新方法)允许您指定用于姿态估计的参考框架。
1 2 3 4 5 6 7 8 9
| CMMotionManager *motionManager = [[CMMotionManager alloc] init]; motionManager.deviceMotionUpdateInterval = 1/15.0; if (motionManager.deviceMotionAvailable) { [motionManager startDeviceMotionUpdates]; double x = motionManager.deviceMotion.gravity.x; double y = motionManager.deviceMotion.gravity.y; double z = motionManager.deviceMotion.gravity.z; NSLog(@"x:%f, y:%f, z:%f", x, y, z); }
|
CMDeviceMotion
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @interface CMDeviceMotion : CMLogItem
@property(readonly, nonatomic) CMAttitude *attitude;
@property(readonly, nonatomic) CMRotationRate rotationRate;
@property(readonly, nonatomic) CMAcceleration gravity;
@property(readonly, nonatomic) CMAcceleration userAcceleration;
@property(readonly, nonatomic) CMCalibratedMagneticField magneticField COREMOTION_EXPORT API_AVAILABLE(ios(5.0));
@property(readonly, nonatomic) double heading COREMOTION_EXPORT API_AVAILABLE(ios(11.0));
@property(readonly, nonatomic) CMDeviceMotionSensorLocation sensorLocation COREMOTION_EXPORT API_AVAILABLE(ios(14.0)); @end
|
硬件可用性和状态
如果某个硬件功能(例如陀螺仪)在设备上不可用,则调用与该功能相关的启动方法无效。您可以通过检查相应的属性来了解硬件功能是否可用或处于活动状态;例如,对于陀螺仪数据,您可以检查isGyroAvailable
或isGyroActive
属性的值。
3.实现方式
- 图片分层
- 通过传感器获取偏移角度
- 计算偏移量,更新图片位置
4.核心代码
iOS 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| #import <CoreMotion/CoreMotion.h> @property (nonatomic, strong) CMMotionManager *motionManager; static const CGFloat CRMotionDeviceMotionMinimumTreshold = 0.f;
static const CGFloat CRMotionDeviceMotionUpdateInterval = 0.02;
static const CGFloat CRMotionDeviceMotionXLFactor = 1.0/(M_PI_4/M_PI_2);
static const CGFloat CRMotionDeviceMotionXRFactor = 1.0/(M_PI_4/M_PI_2);
static const CGFloat CRMotionDeviceMotionYBFactor = 1.0/(M_PI/3/M_PI_2);
static const CGFloat CRMotionDeviceMotionYTFactor = 1.0/(M_PI_4/M_PI_2);
static const CGFloat CRMotionDeviceMotionXLimit = 20.0f;
static const CGFloat CRMotionDeviceMotionYLimit = 12.0f;
- (void)setData { if (self.imgURLs.count > 0) { [self startMonitor]; } else { [self.motionManager stopDeviceMotionUpdates]; } }
- (void)startMonitor { if (!_motionManager) { _motionManager = [[CMMotionManager alloc] init]; _motionManager.deviceMotionUpdateInterval = CRMotionDeviceMotionUpdateInterval; } CGFloat xl = CRMotionDeviceMotionXLFactor; CGFloat xr = CRMotionDeviceMotionXRFactor; CGFloat yt = CRMotionDeviceMotionYTFactor; CGFloat yb = CRMotionDeviceMotionYBFactor; CGFloat oy = (M_PI_2/3)/M_PI_2*1.0; CGFloat ox = 0*1.0; if (![_motionManager isDeviceMotionActive] && [_motionManager isDeviceMotionAvailable]) { @weakify(self); [_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) { @strongify(self); double gravityX = motion.gravity.x+ox; double gravityY = motion.gravity.y+oy; CGFloat maximumYOffset = CRMotionDeviceMotionYLimit; CGFloat minimumYOffset = -CRMotionDeviceMotionYLimit; CGFloat maximumXOffset = CRMotionDeviceMotionXLimit; CGFloat minimumXOffset = -CRMotionDeviceMotionXLimit;
if (fabs(gravityX) >= CRMotionDeviceMotionMinimumTreshold) { CGFloat fOffsetX = CRMotionDeviceMotionXLimit*gravityX*xr; if (gravityX<=0) { fOffsetX = CRMotionDeviceMotionXLimit*gravityX*xl; } CGFloat fOffsetY = -CRMotionDeviceMotionYLimit*gravityY*yt; if (gravityY<=0) { fOffsetY = -CRMotionDeviceMotionYLimit*gravityY*yb; } if (fOffsetX > maximumXOffset) { fOffsetX = maximumXOffset; } else if (fOffsetX < minimumXOffset) { fOffsetX = minimumXOffset; } if (fOffsetY > maximumYOffset) { fOffsetY = maximumYOffset; } else if (fOffsetY < minimumYOffset) { fOffsetY = minimumYOffset; } CGFloat bOffsetX = -fOffsetX; CGFloat bOffsetY = -fOffsetY; if (self.bannerView.scrollView.transform.tx != fOffsetX || self.bannerView.scrollView.transform.ty != fOffsetY) { self.bannerView.scrollView.transform = CGAffineTransformMakeTranslation(fOffsetX, fOffsetY); self.backImageView.transform = CGAffineTransformMakeTranslation(bOffsetX, bOffsetY); self.frontImageView.transform = CGAffineTransformMakeTranslation(bOffsetX,bOffsetY); } } }]; } else { self.bannerView.scrollView.transform = CGAffineTransformMakeTranslation(0, 0); self.backImageView.transform = CGAffineTransformMakeTranslation(0, 0); self.frontImageView.transform = CGAffineTransformMakeTranslation(0,0); } }
|
Android 实现
1.注册对应的传感器
1 2 3 4 5 6 7 8
| mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
mAcceleSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mSensorManager.registerListener(this, mAcceleSensor, SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mMagneticSensor, SensorManager.SENSOR_DELAY_GAME);
|
2.通过重力传感器和地磁场传感器,获取设备的偏转角度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { mAcceleValues = event.values; } if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { mMageneticValues = event.values; }
float[] values = new float[3]; float[] R = new float[9]; SensorManager.getRotationMatrix(R, null, mAcceleValues, mMageneticValues); SensorManager.getOrientation(R, values);
values[1] = (float) Math.toDegrees(values[1]);
values[2] = (float) Math.toDegrees(values[2]);
|
3.根据偏转角度执行滑动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| if (mDegreeY <= 0 && mDegreeY > mDegreeYMin) { hasChangeX = true; scrollX = (int) (mDegreeY / Math.abs(mDegreeYMin) * mXMoveDistance*mDirection); } else if (mDegreeY > 0 && mDegreeY < mDegreeYMax) { hasChangeX = true; scrollX = (int) (mDegreeY / Math.abs(mDegreeYMax) * mXMoveDistance*mDirection); }
if (mDegreeX <= 0 && mDegreeX > mDegreeXMin) { hasChangeY = true; scrollY = (int) (mDegreeX / Math.abs(mDegreeXMin) * mYMoveDistance*mDirection); } else if (mDegreeX > 0 && mDegreeX < mDegreeXMax) { hasChangeY = true; scrollY = (int) (mDegreeX / Math.abs(mDegreeXMax) * mYMoveDistance*mDirection); } smoothScrollTo(hasChangeX ? scrollX : mScroller.getFinalX(), hasChangeY ? scrollY : mScroller.getFinalY());
|